#!/bin/sh -efu
#
# Copyright (C) 2007,2008  Dmitry V. Levin <ldv@altlinux.org>
# Copyright (C) 2019       Gleb Fotengauer-Malinovskiy <glebfm@altlinux.org>
#
# The mki-fakedev utility for the mkimage project
#
# This file is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
#

. mki-sh-functions

show_help()
{
	cat <<EOF
mki-fakedev - create fake device file of the given type in chroot prepared by hsh-initroot.

Usage: $PROG [options] [<path-to-workdir>] [<name> <type> <major> <minor>]

<path-to-workdir> must be valid writable directory.
Arguments <name>, <type>, <major> and <minor> have the same meaning as in
mknod(1) utility.  They are required iff --reference option is not given.

Options:
  -m, --mode=MODE           set permission mode as in mknod(1);
  -g, --group=GROUP         set group ownership as in install(1);
  -o, --owner=OWNER         set ownership as in install(1);
  -r, --reference=FILE      use this file properties
  -q, --quiet               try to be more quiet;
  -v, --verbose             print a message for each action;
  -V, --version             print program version and exit;
  -h, --help                show this text and exit.

Report bugs to http://bugs.altlinux.ru/

EOF
	exit
}

opt_check_dev()
{
	local value
	value="$(readlink -ev -- "$2")" &&
		[ -b "$value" ] || [ -c "$value" ] || [ -p "$value" ] ||
		fatal "$1: $2: device not available."
	printf %s "$value"
}

TEMP=`getopt -n $PROG -o g:,m:,o:,r:,$getopt_common_opts -l mode:,group:,owner:,reference:,$getopt_common_longopts -- "$@"` ||
	show_usage
eval set -- "$TEMP"

mode=
group=
owner=
reference=
while :; do
	case "$1" in
		-m|--mode)
			mode="$2"
			shift
			;;
		-g|--group)
			group="$2"
			shift
			;;
		-o|--owner)
			owner="$2"
			shift
			;;
		-r|--reference)
			reference="$(opt_check_dev "$1" "$2")"
			shift
			;;
		--) shift; break
			;;
		*) parse_common_option "$1"
			;;
	esac
	shift
done

if [ -d "${1:-}" ]; then
	workdir="${1:-}"
	shift
else
	workdir="$HOME/hasher"
fi
chroot="$workdir/chroot"
entry="$chroot/.host/entry"

name=
type=
major=
minor=
if [ -z "$reference" ]; then
	# Exactly 2 arguments for type p, exactly 4 arguments for devices.
	[ "$#" -ge 2 ] || show_usage 'Insufficient arguments.'
	[ "$#" -le 4 ] || show_usage 'Too many arguments.'

	name="$1" && shift
	name="${name#/dev/}"
	[ -n "${name##*/*}" ] && [ -n "${name##*.*}" ] ||
		fatal "$name: invalid device file name"

	type="$1" && shift
	case "$type" in
		b|c|u)
			[ "$#" -eq 2 ] || show_usage 'Insufficient arguments.'

			major="$1" && shift
			[ "$major" -gt 0 ] 2>/dev/null ||
				fatal "$major: invalid device file major number"

			minor="$1" && shift
			[ "$minor" -ge 0 ] 2>/dev/null ||
				fatal "$minor: invalid device file minor number"
			;;
		p)
			[ "$#" -eq 0 ] || show_usage 'Too many arguments.'
			major=
			minor=
			;;
		*)
			fatal "$type: invalid device file type"
			;;
	esac
else
	# No more arguments.
	[ "$#" -eq 0 ] || show_usage 'Too many arguments.'

	TEMP="$(LANG=C stat -c '%a %U %G %t %T %F' "$reference")"
	# 666 root root 1 3 character special file
	case "$TEMP" in
		*\ special\ file|*\ fifo)
			;;
		*)
			fatal "$reference: unrecognized file type: $TEMP"
			;;
	esac
	eval set -- "$TEMP"
	[ "$#" -ge 6 ] ||
		fatal "$reference: unrecognized file type: $TEMP"

	[ -n "$mode" ] || mode="$1"
	shift

	[ -n "$owner" ] || owner="$1"
	shift

	[ -n "$group" ] || group="$1"
	shift

	major="$1" && shift
	[ "$major" -ge 0 ] 2>/dev/null ||
		fatal "$major: invalid device file major number"

	minor="$1" && shift
	[ "$minor" -ge 0 ] 2>/dev/null ||
		fatal "$minor: invalid device file minor number"

	case "$1" in
		block) type=b;;
		character) type=c;;
		fifo) type=p; major=; minor=;;
		*) fatal "$reference: unrecognized file type: $1";;
	esac
	shift

	name="${reference##*/}"
fi

[ -d "$chroot" ] || fatal "$chroot: cannot find chroot."
[ -f "$chroot/.fakedata" ] ||
	fatal "$chroot: chroot was not created using --save-fakeroot option."
dev_name="$chroot/dev/$name"
[ ! -e "$dev_name" ] ||
	fatal "$dev_name: File exists."

[ -n "$owner" ] || owner=0
[ -n "$group" ] || group=0

make_exec "$chroot/.host/fakedev" <<__EOF__
#!/bin/sh -ef
cd \$TMPDIR
mknod -- fakedev "$type" $major $minor
if [ -n "$mode" ]; then
	chmod $mode fakedev
fi
chown -- $owner:$group fakedev
__EOF__

WORKDIR="$workdir" mki-run /.host/fakedev ||
	fatal 'Fake device creation failed.'

touch "$dev_name"
dev_inode="$(stat -c %i -- "$dev_name")"

make_exec "$chroot/.host/fakedev" <<__EOF__
#!/bin/sh -ef
cd \$TMPDIR
tmp_inode="\$(stat -c %i fakedev)"
rm fakedev
rm -f /.fakedata.
sed "s/,ino=\$tmp_inode,/,ino=$dev_inode,/" /.fakedata > /.fakedata.
__EOF__

WORKDIR="$workdir" mki-run /.host/fakedev ||
	fatal 'Fake device creation failed.'

mv -f -- "$chroot/.fakedata." "$chroot/.fakedata"

verbose 'Fake device creation complete.'

exit 0
